package com.blb.edugatewayservice.filter;

import com.alibaba.cloud.commons.io.Charsets;
import com.alibaba.fastjson.JSON;
import com.blb.common.entity.User;
import com.blb.common.util.*;
import com.blb.edugatewayservice.config.GatewayConfig;
import com.blb.edugatewayservice.feign.UserServiceFeignClient;
import io.jsonwebtoken.ExpiredJwtException;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 请求验证过滤器
 */
@Slf4j
@Component
public class RequestAuthenticationFilter implements GlobalFilter, Ordered {

    public static final String ACCESS_TOKEN_HEADER = "Authorization";
    public static final String REFRESH_TOKEN_HEADER = "RefreshToken";

    //白名单
    @Autowired
    private GatewayConfig gatewayConfig;

    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    @Autowired
    private UserServiceFeignClient userServiceFeignClient;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        //跳过白名单
        List<String> whiteList = gatewayConfig.getWhiteList();
        for (String list : whiteList) {
            if (request.getURI().toString().contains(list)) {
                return chain.filter(exchange);
            }
        }
        //从请求头获得token
        String accessToken = request.getHeaders().getFirst(ACCESS_TOKEN_HEADER);
        try {
            //对token进行解析
            User user = JwtUtils.getUserFromToken(accessToken, RsaUtils.publicKey);
            log.info("Token解析成功，{}登录成功", user);
            return chain.filter(exchange);
        } catch (ExpiredJwtException ex) {
            //如果access-token过时，则解析refresh-token
            String refreshToken = request.getHeaders().getFirst(REFRESH_TOKEN_HEADER);
            if(StringUtils.isEmpty(refreshToken)){
                log.info("读取不到refreshToken，请求{}被拦截",request.getURI());
            }else {
                try {
                    //解析refreshToken加密的字符串
                    String refresh = JwtUtils.getStringFromToken(refreshToken, RsaUtils.publicKey);
                    //读取redis中保存的userId
                    String userId = redisTemplate.opsForValue().get(UserTokenUtils.TOKEN_PREFIX + refresh);
                    if(StringUtils.isEmpty(userId)){
                        throw new RuntimeException("解析token失败");
                    }
                    //读取用户数据
                    ResponseResult<User> result = userServiceFeignClient.getUserById(Long.valueOf(userId));
                    User user = result.getData();
                    if(user == null){
                        throw new RuntimeException("解析token失败");
                    }
                    //删除旧的refresh-token
                    redisTemplate.opsForValue().set(UserTokenUtils.TOKEN_PREFIX + refresh,null);
                    //生成新access-token，refresh-token
                    ResponseResult<User> responseResult = UserTokenUtils.generateUserToken(user, redisTemplate);
                    //将新的token包装到响应正文中返回客户端
                    return chain.filter(exchange.mutate().response(responseDecorator(response,
                            responseResult.getAccessToken(), responseResult.getRefreshToken())).build());
                } catch (Exception ex2) {
                    log.error("解析token失败", ex);
                }
            }
        } catch (Exception ex) {
            log.error("解析token失败",ex);
        }
        // 出现错误进行拦截
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        //返回验证失败的响应信息给客户端
        DataBuffer wrap = response.bufferFactory().wrap(ResponseStatus.AUTH_ERROR.getMessage().getBytes());
        return response.writeWith(Mono.just(wrap));
    }

    @Override
    public int getOrder() {
        return -1;
    }

    /**
     * 返回响应装饰器对象，添加token数据
     * @param response
     * @param accessToken
     * @param refreshToken
     * @return
     */
    public ServerHttpResponseDecorator responseDecorator(ServerHttpResponse response,String accessToken,String refreshToken){
        // 缓存数据的工厂
        DataBufferFactory bufferFactory = response.bufferFactory();
        // 拿到响应码
        HttpStatus statusCode = response.getStatusCode();
        // 返回响应装饰器对象
        return new ServerHttpResponseDecorator(response){
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                if (statusCode.equals(HttpStatus.OK) && body instanceof Flux) {
                    Flux<? extends DataBuffer> fluxBody = Flux.from(body);
                    return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
                        // 读取原有的数据
                        DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
                        DataBuffer join = dataBufferFactory.join(dataBuffers);
                        byte[] content = new byte[join.readableByteCount()];
                        join.read(content);
                        DataBufferUtils.release(join);
                        // 流转为字符串
                        String responseData = new String(content, Charsets.UTF_8);
                        Map map = JSON.parseObject(responseData);
                        //处理返回的数据，添加token
                        map.put("accessToken",accessToken);
                        map.put("refreshToken",refreshToken);
                        responseData = JSON.toJSONString(map);
                        log.info("网关过滤响应数据：{}",responseData);
                        //将添加了token的响应数据返回
                        byte[] uppedContent = responseData.getBytes(Charsets.UTF_8);
                        response.getHeaders().setContentLength(uppedContent.length);
                        return bufferFactory.wrap(uppedContent);
                    }));
                } else {
                    log.info("错误的响应码{}",statusCode);
                }
                return super.writeWith(body);
            }
        };
    }
}
